package java_thinking.unit_21._21_4;

import net.jcip.annotations.NotThreadSafe;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @Desc:   4-14    非线程安全的"若没有则添加" 不要这么做
 *                  这是客户端加锁机制-扩展类的功能，当并不是扩展类本身，而是将扩展代码放入一个辅助类中
 *                  本案例网络解读：https://blog.csdn.net/m0_38110132/article/details/81353664
 *                  需要理解类锁、方法锁、对象锁 https://www.cnblogs.com/shoshana-kong/p/10551952.html
 * @author: cww
 * @DateTime: 2019/6/3 19:46
 */
@NotThreadSafe
public class ListHelper<E> {
    /**
     * 肯定的是 Collections.synchronizedList 返回的线程安全的 List 内部使用的锁
     * 绝对不是 ListHelper 对象的锁
     */
    public List<E> list = Collections.synchronizedList(new ArrayList<>());

    /**
     * 表面上 putIfAbsent 声明为 synchronized 但问题在于错误的锁上进行了同步，
     * 无论List使用哪个锁来保护它的状态，可以确定的是，这个锁并不是*来自*ListHelper
     * 的锁！！！，ListHelper 只是带来了同步的假象，尽管所有的链表操作都声明为
     * synchronized ，但却使用了不同的锁，这意味着 putIfAbsent 相对于List其他
     * 操作来说并不是原子的，因此无法确保当 putIfAbsent 执行另一个线程不会修改
     * 链表
     * @param x
     * @ synchronized  它是基于当前的对象来加锁的
     * @return
     */
    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !list.contains(x);
        if (absent) {
            list.add(x);
        }
        return absent;
    }

    /**
     * 这样写和上面的效果是一样的，是对对象加锁 ListHelper 对象
     * 而 list 对象的锁是来自 SynchronizedRandomAccessList 详见源码或上方解读地址
     * 正确的客户端加锁方式 SafeListHelper.java
     * @param x
     * @return
     */
    public boolean putIfAbsent1(E x) {
        synchronized(this) {
            boolean absent = !list.contains(x);
            if (absent)
                list.add(x);
            return absent;
        }
    }
}
